{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "\n",
    "import numpy as np\n",
    "import xarray as xr\n",
    "\n",
    "# prepend the parent directory so it'll find the local climate_indices in the path\n",
    "if \"/home/james/git/climate_indices\" not in sys.path:\n",
    "    sys.path.insert(0, \"/home/james/git/climate_indices\")\n",
    "from climate_indices import compute, indices, utils\n",
    "\n",
    "%matplotlib inline\n",
    "%load_ext autoreload"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['/home/james/git/climate_indices',\n",
       " '/home/james/git/climate_indices/notebooks',\n",
       " '/home/james/miniconda3/envs/spi_fit/lib/python37.zip',\n",
       " '/home/james/miniconda3/envs/spi_fit/lib/python3.7',\n",
       " '/home/james/miniconda3/envs/spi_fit/lib/python3.7/lib-dynload',\n",
       " '',\n",
       " '/home/james/miniconda3/envs/spi_fit/lib/python3.7/site-packages',\n",
       " '/home/james/miniconda3/envs/spi_fit/lib/python3.7/site-packages/IPython/extensions',\n",
       " '/home/james/.ipython']"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# %autoreload 2\n",
    "sys.path"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# open the precipitation NetCDF as an xarray DataSet object\n",
    "ds_lo = xr.open_dataset(\"/data/datasets/nclimgrid/nclimgrid_lowres_prcp.nc\")\n",
    "ds_hi = xr.open_dataset(\"/data/datasets/nclimgrid/nclimgrid_prcp.nc\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found\n"
     ]
    }
   ],
   "source": [
    "from climate_indices.compute import test_function\n",
    "\n",
    "test_function(\"Found\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get the precipitation arrays, over which we'll compute the SPI\n",
    "da_precip_lo = ds_lo[\"prcp\"]\n",
    "da_precip_hi = ds_hi[\"prcp\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# make sure we have the arrays with time as the inner-most dimension\n",
    "preferred_dims = (\"lat\", \"lon\", \"time\")\n",
    "da_precip_lo = da_precip_lo.transpose(*preferred_dims)\n",
    "da_precip_hi = da_precip_hi.transpose(*preferred_dims)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The high-resolution DataArray's lats are descending -- flipping\n"
     ]
    }
   ],
   "source": [
    "# we need for the lat and lon values to be in ascending order\n",
    "# in order for the xr.apply_ufunc() to work as expected\n",
    "# see https://stackoverflow.com/questions/53108606/xarray-apply-ufunc-with-groupby-unexpected-number-of-dimensions\n",
    "data_arrays = {\n",
    "    \"low\": da_precip_lo,\n",
    "    \"high\": da_precip_hi,\n",
    "}\n",
    "for label, da in data_arrays.items():\n",
    "    if da[\"lat\"][0] > da[\"lat\"][1]:\n",
    "        print(f\"The {label}-resolution DataArray's lats are descending -- flipping\")\n",
    "        da[\"lat\"] = np.flip(da[\"lat\"])\n",
    "    if da[\"lon\"][0] > da[\"lon\"][1]:\n",
    "        print(f\"The {label}-resolution DataArray's lons are descending -- flipping\")\n",
    "        da[\"lon\"] = np.flip(da[\"lon\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "initial_year = int(da_precip_lo[\"time\"][0].dt.year)\n",
    "scale_months = 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "total_lats = len(da_precip_lo[\"lat\"])\n",
    "total_lons = len(da_precip_lo[\"lon\"])\n",
    "monthly_vals_shape = (total_lats, total_lons, 12)\n",
    "alphas = np.full(shape=monthly_vals_shape, fill_value=np.NaN)\n",
    "betas = np.full(shape=monthly_vals_shape, fill_value=np.NaN)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "from climate_indices.compute import Periodicity, scale_values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 10min 53s, sys: 11.7 s, total: 11min 5s\n",
      "Wall time: 11min 5s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "# loop over each lat/lon point and compute the gamma fitting parameters alpha and beta for 3-month scaled data\n",
    "for lat_index in range(total_lats):\n",
    "    for lon_index in range(total_lons):\n",
    "        # get the values for the lat/lon grid cell\n",
    "        values = da_precip_lo[lat_index, lon_index]\n",
    "\n",
    "        # skip over this grid cell if all NaN values\n",
    "        if (np.ma.is_masked(values) and values.mask.all()) or np.all(np.isnan(values)):\n",
    "            continue\n",
    "\n",
    "        # scale to 3-month convolutions\n",
    "        scaled_values = scale_values(values, scale=3, periodicity=Periodicity.monthly)\n",
    "\n",
    "        # compute the fitting parameters on the scaled data\n",
    "        (\n",
    "            alphas[lat_index, lon_index],\n",
    "            betas[lat_index, lon_index],\n",
    "        ) = compute.gamma_parameters(\n",
    "            scaled_values,\n",
    "            data_start_year=initial_year,\n",
    "            calibration_start_year=1900,\n",
    "            calibration_end_year=2000,\n",
    "            periodicity=compute.Periodicity.monthly,\n",
    "        )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "spi_gamma_3month = np.full(shape=da_precip_lo.shape, fill_value=np.NaN)\n",
    "gamma_params = {\"alphas\": alphas, \"betas\": betas}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(38, 87, 1466)"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "da_precip_lo.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "operands could not be broadcast together with shapes (123,12) (38,87,12) ",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[0;32m<timed exec>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n",
      "\u001b[0;32m~/miniconda3/envs/spi_fit/lib/python3.7/site-packages/scipy/stats/_distn_infrastructure.py\u001b[0m in \u001b[0;36mcdf\u001b[0;34m(self, x, *args, **kwds)\u001b[0m\n\u001b[1;32m   1802\u001b[0m         \u001b[0margs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0masarray\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1803\u001b[0m         \u001b[0mdtyp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfind_common_type\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdtype\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfloat64\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1804\u001b[0;31m         \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0masarray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mloc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0mscale\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdtype\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdtyp\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1805\u001b[0m         \u001b[0mcond0\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_argcheck\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mscale\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1806\u001b[0m         \u001b[0mcond1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_open_support_mask\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mscale\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mValueError\u001b[0m: operands could not be broadcast together with shapes (123,12) (38,87,12) "
     ]
    }
   ],
   "source": [
    "%%time\n",
    "# loop over each lat/lon point and compute the gamma fitting parameters alpha and beta for 3-month scaled data\n",
    "for lat_index in range(total_lats):\n",
    "    for lon_index in range(total_lons):\n",
    "        # get the values for the lat/lon grid cell\n",
    "        values = da_precip_lo[lat_index, lon_index]\n",
    "\n",
    "        # skip over this grid cell if all NaN values\n",
    "        if (np.ma.is_masked(values) and values.mask.all()) or np.all(np.isnan(values)):\n",
    "            continue\n",
    "\n",
    "        # compute SPI/gamma for the 3-month scale\n",
    "        spi_gamma_3month[lat_index, lon_index] = indices.spi(\n",
    "            values,\n",
    "            scale=scale_months,\n",
    "            distribution=indices.Distribution.gamma,\n",
    "            data_start_year=initial_year,\n",
    "            calibration_year_initial=1900,\n",
    "            calibration_year_final=2000,\n",
    "            periodicity=compute.Periodicity.monthly,\n",
    "            fitting_params=gamma_params,\n",
    "        )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "da_precip_lo_groupby = da_precip_lo.stack(point=(\"lat\", \"lon\")).groupby(\"point\")\n",
    "da_precip_hi_groupby = da_precip_hi.stack(point=(\"lat\", \"lon\")).groupby(\"point\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "# use xarray's apply_ufunc to apply a function over each point\n",
    "da_one_more_lo = xr.apply_ufunc(add_one, da_precip_lo_groupby)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "da_one_more_lo_looper = da_precip_lo.copy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "# do it \"by hand\" by looping over each lat/lon point\n",
    "for lat_index in range(len(da_one_more_lo_looper[\"lat\"])):\n",
    "    for lon_index in range(len(da_one_more_lo_looper[\"lon\"])):\n",
    "        da_one_more_lo_looper[lat_index, lon_index] = add_one(da_one_more_lo_looper[lat_index, lon_index])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "# use xarray's apply_ufunc to apply a function over each point\n",
    "da_one_more_hi = xr.apply_ufunc(add_one, da_precip_hi_groupby)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "da_one_more_hi_looper = da_precip_hi.copy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "# do it \"by hand\" by looping over each lat/lon point\n",
    "for lat_index in range(len(da_one_more_hi_looper[\"lat\"])):\n",
    "    for lon_index in range(len(da_one_more_hi_looper[\"lon\"])):\n",
    "        da_one_more_hi_looper[lat_index, lon_index] = add_one(da_one_more_hi_looper[lat_index, lon_index])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "_FITTED_INDEX_VALID_MIN = -3.09\n",
    "_FITTED_INDEX_VALID_MAX = 3.09\n",
    "\n",
    "\n",
    "def spi(\n",
    "    values: np.ndarray,\n",
    "    scale: int,\n",
    "    distribution: indices.Distribution,\n",
    "    data_start_year: int,\n",
    "    calibration_year_initial: int,\n",
    "    calibration_year_final: int,\n",
    "    periodicity: compute.Periodicity,\n",
    ") -> np.ndarray:\n",
    "    \"\"\"\n",
    "    Computes SPI (Standardized Precipitation Index).\n",
    "\n",
    "    :param values: 1-D numpy array of precipitation values, in any units,\n",
    "        first value assumed to correspond to January of the initial year if\n",
    "        the periodicity is monthly, or January 1st of the initial year if daily\n",
    "    :param scale: number of time steps over which the values should be scaled\n",
    "        before the index is computed\n",
    "    :param distribution: distribution type to be used for the internal\n",
    "        fitting/transform computation\n",
    "    :param data_start_year: the initial year of the input precipitation dataset\n",
    "    :param calibration_year_initial: initial year of the calibration period\n",
    "    :param calibration_year_final: final year of the calibration period\n",
    "    :param periodicity: the periodicity of the time series represented by the\n",
    "        input data, valid/supported values are 'monthly' and 'daily'\n",
    "        'monthly' indicates an array of monthly values, assumed to span full\n",
    "         years, i.e. the first value corresponds to January of the initial year\n",
    "         and any missing final months of the final year filled with NaN values,\n",
    "         with size == # of years * 12\n",
    "         'daily' indicates an array of full years of daily values with 366 days\n",
    "         per year, as if each year were a leap year and any missing final months\n",
    "         of the final year filled with NaN values, with array size == (# years * 366)\n",
    "    :param fitting_params: optional dictionary of pre-computed distribution\n",
    "        fitting parameters, if the distribution is gamma then this dict should\n",
    "        contain two arrays, keyed as \"alphas\" and \"betas\", and if the\n",
    "        distribution is Pearson then this dict should contain four arrays keyed\n",
    "        as \"probabilities_of_zero\", \"locs\", \"scales\", and \"skews\"\n",
    "    :return SPI values fitted to the gamma distribution at the specified time\n",
    "        step scale, unitless\n",
    "    :rtype: 1-D numpy.ndarray of floats of the same length as the input array\n",
    "        of precipitation values\n",
    "    \"\"\"\n",
    "\n",
    "    # we expect to operate upon a 1-D array, so if we've been passed a 2-D array\n",
    "    # then we flatten it, otherwise raise an error\n",
    "    shape = values.shape\n",
    "    if len(shape) == 2:\n",
    "        values = values.flatten()\n",
    "    elif len(shape) != 1:\n",
    "        message = f\"Invalid shape of input array: {shape}\" + \" -- only 1-D and 2-D arrays are supported\"\n",
    "        _logger.error(message)\n",
    "        raise ValueError(message)\n",
    "\n",
    "    # if we're passed all missing values then we can't compute\n",
    "    # anything, so we return the same array of missing values\n",
    "    if (np.ma.is_masked(values) and values.mask.all()) or np.all(np.isnan(values)):\n",
    "        return values\n",
    "\n",
    "    # clip any negative values to zero\n",
    "    if np.amin(values) < 0.0:\n",
    "        _logger.warn(\"Input contains negative values -- all negatives clipped to zero\")\n",
    "        values = np.clip(values, a_min=0.0, a_max=None)\n",
    "\n",
    "    # remember the original length of the array, in order to facilitate\n",
    "    # returning an array of the same size\n",
    "    original_length = values.size\n",
    "\n",
    "    # get a sliding sums array, with each time step's value scaled\n",
    "    # by the specified number of time steps\n",
    "    values = compute.sum_to_scale(values, scale)\n",
    "\n",
    "    # reshape precipitation values to (years, 12) for monthly,\n",
    "    # or to (years, 366) for daily\n",
    "    if periodicity is compute.Periodicity.monthly:\n",
    "        values = utils.reshape_to_2d(values, 12)\n",
    "\n",
    "    elif periodicity is compute.Periodicity.daily:\n",
    "        values = utils.reshape_to_2d(values, 366)\n",
    "\n",
    "    else:\n",
    "        raise ValueError(\"Invalid periodicity argument: %s\" % periodicity)\n",
    "\n",
    "    if distribution is indices.Distribution.gamma:\n",
    "        # fit the scaled values to a gamma distribution\n",
    "        # and transform to corresponding normalized sigmas\n",
    "        values = compute.transform_fitted_gamma(\n",
    "            values,\n",
    "            data_start_year,\n",
    "            calibration_year_initial,\n",
    "            calibration_year_final,\n",
    "            periodicity,\n",
    "        )\n",
    "    elif distribution is indices.Distribution.pearson:\n",
    "        # fit the scaled values to a Pearson Type III distribution\n",
    "        # and transform to corresponding normalized sigmas\n",
    "        values = compute.transform_fitted_pearson(\n",
    "            values,\n",
    "            data_start_year,\n",
    "            calibration_year_initial,\n",
    "            calibration_year_final,\n",
    "            periodicity,\n",
    "        )\n",
    "\n",
    "    else:\n",
    "        message = \"Unsupported distribution argument: \" + f\"{distribution}\"\n",
    "        _logger.error(message)\n",
    "        raise ValueError(message)\n",
    "\n",
    "    # clip values to within the valid range, reshape the array back to 1-D\n",
    "    values = np.clip(values, _FITTED_INDEX_VALID_MIN, _FITTED_INDEX_VALID_MAX).flatten()\n",
    "\n",
    "    # return the original size array\n",
    "    return values[0:original_length]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def apply_spi_gamma_monthly(\n",
    "    data_array: xr.DataArray,\n",
    "    months: int,\n",
    "    data_start_year: int = 1895,\n",
    "    calibration_year_initial: int = 1900,\n",
    "    calibration_year_final: int = 2000,\n",
    ") -> xr.DataArray:\n",
    "    # stack the lat and lon dimensions into a new dimension named point, so at each lat/lon\n",
    "    # we'll have a time series for the geospatial point, and group by these points\n",
    "    da_precip_groupby = data_array.stack(point=(\"lat\", \"lon\")).groupby(\"point\")\n",
    "\n",
    "    spi_args = {\n",
    "        \"scale\": months,\n",
    "        \"distribution\": indices.Distribution.gamma,\n",
    "        \"data_start_year\": data_start_year,\n",
    "        \"calibration_year_initial\": calibration_year_initial,\n",
    "        \"calibration_year_final\": calibration_year_final,\n",
    "        \"periodicity\": compute.Periodicity.monthly,\n",
    "    }\n",
    "\n",
    "    # apply the SPI function to the data array\n",
    "    da_spi = xr.apply_ufunc(\n",
    "        spi,\n",
    "        da_precip_groupby,\n",
    "        kwargs=spi_args,\n",
    "    )\n",
    "\n",
    "    # unstack the array back into original dimensions\n",
    "    da_spi = da_spi.unstack(\"point\")\n",
    "\n",
    "    return da_spi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "da_spi_lo = apply_spi_gamma_monthly(da_precip_lo, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "da_spi_lo[200].plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "da_precip_hi = da_precip_hi.transpose(\"lat\", \"lon\", \"time\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "da_precip_hi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "da_precip_lo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "da_spi_hi = apply_spi_gamma_monthly(da_precip_hi, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
